package org.fxml.helpers {
	import org.fxml.Application;
	import org.fxml.constants.Keywords;
	import org.fxml.events.ApplicationEvent;
	import org.fxml.gateways.ReflectionGateway;
	import org.fxml.utils.BooleanUtil;
	import org.fxml.utils.ObjectUtil;
	import org.fxml.utils.getDefinitionByName;

	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Proxy;

	/**
	 * @author jordandoczy
	 */
	public class ClassBuilderFactory extends EventDispatcher {
		
		protected var _target:Application;
		
		protected var nullBuilder:AbstractBuilder;
		protected var baseBuilder:AbstractBuilder;
		protected var displayObjectContainerBuilder:AbstractBuilder;
		protected var arrayBuilder:AbstractBuilder;
		protected var spriteBuilder:AbstractBuilder;
		
		public function ClassBuilderFactory(target:Application){
			_target = target;
			createBuilders();
		}
		
		protected function castAsArray(xml:XML):XML{
			xml.@["class"] = "Array";
			delete xml.@["function"];
			delete xml.@["instance"];
			delete xml.@["reference"];
			return xml;
		}
		
		protected function convertValue(xml:XML, builder:IBuilder=null):*{
			var instance:*;
			
			if(xml.hasOwnProperty("@"+Keywords.CLASS)){
				return instantiateObject(xml);
			}
			else if(xml.hasOwnProperty("@"+Keywords.INSTANCE)){
				instance = ObjectUtil.getValue(xml.attribute(Keywords.INSTANCE).toString(), _target);
				setProperties(xml, instance);
				return instance;
			}
			else if(xml.hasOwnProperty("@"+Keywords.REFERENCE)){
				return getDefinitionByName(xml.attribute(Keywords.REFERENCE));
			}
			else if(xml.hasOwnProperty("@"+Keywords.FUNCTION)){
				var func:Function;
				try{
					func = ObjectUtil.getValue(xml.attribute(Keywords.FUNCTION).toString(), _target);
				}
				catch(e:Error){
					func = getDefinitionByName(xml.attribute(Keywords.FUNCTION));
				}
				
				var params:Array = (xml.length() > 0) ? createInstance(castAsArray(xml)) : [];
				
				return func.apply(null, params);
			}
			else if(builder){
				return builder.convertValue(xml);
			}
		}
		
		protected function createBuilders():void{
			nullBuilder = new NullBuilder();
			baseBuilder = new BaseBuilder(nullBuilder, Application(_target));
			displayObjectContainerBuilder = new DisplayObjectContainerBuilder(baseBuilder);
			arrayBuilder = new ArrayBuilder(baseBuilder);
			spriteBuilder = new SpriteBuilder(displayObjectContainerBuilder);
		}
		
		public function createInstance(xml : XML):* {
			return convertValue(xml);
		}

		protected function getBuilder(instance:*):IBuilder{
			
				 if(instance is Array)						return arrayBuilder;
			else if(instance is Sprite)						return spriteBuilder;
			else if(instance is DisplayObjectContainer)		return displayObjectContainerBuilder;
			else											return baseBuilder;
		}
		
		protected function instantiateObject(xml:XML):*{
			var params:Array;
			
			if(xml.hasOwnProperty(Keywords.CONSTRUCTOR)) params = createInstance(castAsArray(xml.constructor[0]));
			var instance:* =  ReflectionGateway.createInstance(xml.attribute(Keywords.CLASS), null, params);
			setProperties(xml, instance);
			return instance;
		}
		
		protected function setProperties(xml:XML, instance:*):void{
			var builder:IBuilder = getBuilder(instance);
			var child:XML;
			
			
			if(xml.hasOwnProperty("@"+Keywords.EVENT) && instance is IEventDispatcher){
				var event:String = getDefinitionByName(xml.attribute(Keywords.EVENT)) || xml.attribute(Keywords.EVENT);
				dispatchEvent(new ApplicationEvent(ApplicationEvent.EVENT_ADDED, event, instance as IEventDispatcher));
			}
			
			for each(child in xml.children()){
				if(builder.hasProperty(instance, child.localName()) || instance is Proxy){
					if(builder.isFunction(instance, child.localName())) {
						instance[child.localName()].apply(instance, createInstance(castAsArray(child)));
					}
					else if(child.hasOwnProperty("@"+Keywords.READ_ONLY) && BooleanUtil(child.attribute(Keywords.READ_ONLY))){
						setProperties(child, instance[child.localName()]);
					}
					else if(child.localName() != Keywords.CONSTRUCTOR) {
						builder.setProperty(instance, child.localName(), convertValue(child, builder));
					}
					
					
				}
			}

		}
	}
}
